﻿/*
 * KLF Headless Browser
 * Written by Kris Oye <kristianoye@gmail.com>
 * Copyright (C) 2011-2012.  All rights reserved.
 */
namespace KLF.Lib.Browser.DOM.CSS
{
    #region Using Statements

    using System;
    using System.Text.RegularExpressions;

    #endregion

    /// <summary>
    /// The CSSPrimitiveValue interface represents a single CSS value. This 
    /// interface may be used to determine the value of a specific style 
    /// property currently set in a block or to set a specific style property
    /// explicitly within the block. An instance of this interface might be 
    /// obtained from the getPropertyCSSValue method of the CSSStyleDeclaration
    /// interface. A CSSPrimitiveValue object only occurs in a context of a CSS
    /// property.
    /// 
    /// Conversions are allowed between absolute values (from millimeters to 
    /// centimeters, from degrees to radians, and so on) but not between
    /// relative values. (For example, a pixel value cannot be converted to a 
    /// centimeter value.) Percentage values can't be converted since they are
    /// relative to the parent value (or another property value). There is one 
    /// exception for color percentage values: since a color percentage value 
    /// is relative to the range 0-255, a color percentage value can be
    /// converted to a number; (see also the RGBColor interface).
    /// </summary>
    public class CSSPrimitiveValue : CSSValue
    {
        #region Constants

        // UnitTypes
        public const short CSS_UNKNOWN = 0;
        public const short CSS_NUMBER = 1;
        public const short CSS_PERCENTAGE = 2;
        public const short CSS_EMS = 3;
        public const short CSS_EXS = 4;
        public const short CSS_PX = 5;
        public const short CSS_CM = 6;
        public const short CSS_MM = 7;
        public const short CSS_IN = 8;
        public const short CSS_PT = 9;
        public const short CSS_PC = 10;
        public const short CSS_DEG = 11;
        public const short CSS_RAD = 12;
        public const short CSS_GRAD = 13;
        public const short CSS_MS = 14;
        public const short CSS_S = 15;
        public const short CSS_HZ = 16;
        public const short CSS_KHZ = 17;
        public const short CSS_DIMENSION = 18;
        public const short CSS_STRING = 19;
        public const short CSS_URI = 20;
        public const short CSS_IDENT = 21;
        public const short CSS_ATTR = 22;
        public const short CSS_COUNTER = 23;
        public const short CSS_RECT = 24;
        public const short CSS_RGBCOLOR = 25;

        #endregion

        #region Constructors

        public CSSPrimitiveValue()
            : base()
        {
            cssText = "";
            cssValueType = CSSValue.CSS_PRIMITIVE_VALUE;
        }

        internal CSSPrimitiveValue(int _type, string _rawValue)
            : this()
        {
            primitiveType = _type;
            cssText = _rawValue;
        }

        #endregion

        #region Attributes

        internal string Value { get; set; }

        #endregion

        #region Methods

        static Regex AngleRegex = new Regex(@"^(?<val>[0-9\.]+)(?<unit>deg|rad)");
        static Regex FrequencyRegex = new Regex(@"^(?<val>[0-9\.]+)(?<unit>hz|khz)");
        static Regex LengthRegex = new Regex(@"^(?<val>[0-9\.]+)(?<unit>em|ex|px|pt|pc|cm|mm|in)");
        static Regex TimeRegex = new Regex(@"^(?<val>[0-9\.]+)(?<unit>s|ms|m)");

        internal static CSSPrimitiveValue CreateAnglePrimitive(string rawText)
        {
            CSSPrimitiveValue pv = null;
            Match m = AngleRegex.Match(rawText);

            if (m.Success)
            {
                pv = new CSSPrimitiveValue();
                pv.cssText = rawText;
                pv.Value = m.Groups["val"].Value;

                switch (m.Groups["unit"].Value)
                {
                    case "deg": pv.primitiveType = CSS_DEG; break;
                    case "rad": pv.primitiveType = CSS_RAD; break;
                }
            }

            return pv;
        }

        internal static CSSPrimitiveValue CreateColorPrimitive(string rawText)
        {
            CSSPrimitiveValue pv = new CSSPrimitiveValue();

            pv.primitiveType = CSS_RGBCOLOR;
            pv.cssText = pv.Value = rawText;

            return pv;
        }

        internal static CSSPrimitiveValue CreateFrequencyPrimitive(string rawText)
        {
            CSSPrimitiveValue pv = null;
            Match m = FrequencyRegex.Match(rawText);

            if (m.Success)
            {
                pv = new CSSPrimitiveValue();
                pv.cssText = rawText;
                pv.Value = m.Groups["val"].Value;

                switch (m.Groups["unit"].Value)
                {
                    case "hz": pv.primitiveType = CSS_HZ; break;
                    case "khz": pv.primitiveType = CSS_KHZ; break;
                }
            }

            return pv;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="rawText"></param>
        /// <returns></returns>
        internal static CSSPrimitiveValue CreateLengthPrimitive(string rawText)
        {
            CSSPrimitiveValue pv = null;
            Match m = LengthRegex.Match(rawText = rawText.Trim());

            if (m.Success)
            {
                pv = new CSSPrimitiveValue();
                pv.cssText = rawText;
                pv.Value = m.Groups["val"].Value;

                switch (m.Groups["unit"].Value)
                {
                    case "em": pv.primitiveType = CSS_EMS; break;
                    case "ex": pv.primitiveType = CSS_EXS; break;
                    case "px": pv.primitiveType = CSS_PX; break;
                    case "pt": pv.primitiveType = CSS_PT; break;
                    case "pc": pv.primitiveType = CSS_PC; break;
                    case "cm": pv.primitiveType = CSS_CM; break;
                    case "mm": pv.primitiveType = CSS_MM; break;
                    case "in": pv.primitiveType = CSS_IN; break;
                }
            }

            return pv;
        }

        internal static CSSPrimitiveValue CreateNumberPrimitive(string rawText)
        {
            CSSPrimitiveValue pv = new CSSPrimitiveValue();

            pv.primitiveType = CSS_NUMBER;
            pv.cssText = rawText;
            pv.Value = rawText;

            return pv;
        }

        internal static CSSPrimitiveValue CreatePercentPrimitive(string rawText)
        {
            CSSPrimitiveValue pv = new CSSPrimitiveValue();

            pv.cssText = rawText;
            pv.Value = rawText.Substring(0, rawText.Length - 1);
            pv.primitiveType = CSS_PERCENTAGE;

            return pv;
        }

        internal static CSSPrimitiveValue CreateStringPrimitive(string rawText)
        {
            return CreateStringPrimitive(rawText, CSS_STRING);
        }

        internal static CSSPrimitiveValue CreateStringPrimitive(string rawText, int type)
        {
            CSSPrimitiveValue pv = new CSSPrimitiveValue();

            pv.cssText = pv.Value = rawText;
            pv.primitiveType = type;

            return pv;
        }

        internal static CSSPrimitiveValue CreateTimePrimitive(string rawText)
        {
            CSSPrimitiveValue pv = null;
            Match m = TimeRegex.Match(rawText);

            if (m.Success)
            {
                pv = new CSSPrimitiveValue();
                pv.Value = m.Groups["val"].Value;
                pv.cssText = rawText;

                switch (m.Groups["unit"].Value)
                {
                    case "ms": pv.primitiveType = CSS_MS; break;
                    case "s": pv.primitiveType = CSS_S; break;
                }
            }

            return pv;
        }

        #endregion

        #region DOM Level 2 CSS Implementation

        /// <summary>
        /// This method is used to get the Counter value. If this CSS value 
        /// doesn't contain a counter value, a DOMException is raised. 
        /// Modification to the corresponding style property can be 
        /// achieved using the Counter interface. 
        /// </summary>
        /// <returns>The Counter value.</returns>
        public Counter getCounterValue()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The float value in the specified unit.
        /// </summary>
        /// <param name="unitType"></param>
        /// <param name="floatValue"></param>
        /// <returns></returns>
        public float getFloatValue(int unitType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method is used to get the Rect value. If this CSS value 
        /// doesn't contain a rect value, a DOMException is raised. 
        /// Modification to the corresponding style property can be 
        /// achieved using the Rect interface. 
        /// </summary>
        /// <returns></returns>
        public Rect getRectValue()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method is used to get the RGB color. If this CSS value doesn't
        /// contain a RGB color value, a DOMException is raised. Modification 
        /// to the corresponding style property can be achieved using the 
        /// RGBColor interface. 
        /// </summary>
        /// <returns></returns>
        public RGBColor getRGBColorValue()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method is used to get the string value. If the CSS value 
        /// doesn't contain a string value, a DOMException is raised. 
        /// 
        /// NOTE: Some properties (like 'font-family' or 'voice-family') 
        /// convert a whitespace separated list of idents to a string.
        /// </summary>
        /// 
        /// <returns>The string value in the current unit. The current 
        /// primitiveType can only be a string unit type (i.e. CSS_STRING,
        /// CSS_URI, CSS_IDENT and CSS_ATTR).</returns>
        public string getSringValue()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The type of the value as defined by the constants specified above.
        /// </summary>
        public int primitiveType
        {
            get;
            internal set;
        }

        /// <summary>
        /// A method to set the float value with a specified unit. If the 
        /// property attached with this value can not accept the specified 
        /// unit or the float value, the value will be unchanged and a 
        /// DOMException will be raised.
        /// </summary>
        /// <param name="unitType"></param>
        /// <param name="floatValue"></param>
        public double setFloatValue(int unitType, float floatValue)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// A method to set the float value with a specified unit. If the 
        /// property attached with this value can not accept the specified 
        /// unit or the float value, the value will be unchanged and a 
        /// DOMException will be raised.
        /// </summary>
        /// <param name="unitType"></param>
        /// <param name="floatValue"></param>
        /// <returns></returns>
        internal double setFloatValue(int unitType, string floatValue)
        {
            double f;

            if (Double.TryParse(floatValue, out f))
            {
                cssText = floatValue;
                return f;
            }
            return -1.0;
        }

        internal void setLengthValue(string rawValue)
        {

        }

        /// <summary>
        /// A method to set the string value with the specified unit. If the
        /// property attached to this value can't accept the specified unit or
        /// the string value, the value will be unchanged and a DOMException 
        /// will be raised. 
        /// </summary>
        /// 
        /// <param name="stringType">A string code as defined above. The string
        /// code can only be a string unit type (i.e. CSS_STRING, CSS_URI,
        /// CSS_IDENT, and CSS_ATTR).</param>
        /// 
        /// <param name="stringValue">The new string value.</param>
        /// 
        /// <returns></returns>
        public string setStringValue(int valueType, string stringValue)
        {
            primitiveType = valueType;
            return (cssText = stringValue);
        }

        public override string ToString()
        {
            return cssText;
        }

        #endregion
    }
}
